Introduction

Legend says that after the military victory against the Persians at the Battle of Marathon (490 BC), Greek soldier Pheidippides ran the 42.195 kilometres that separate Marathon and Athens to report the victory. What people seem to forget about this fable is that Pheidippides died immediately after delivering the message. And so long-distance running as we know was born…

This work analyses the 2017 Boston Marathon that took place on April 17th. Using data collected from 26,400 athletes, we will try to identify relationships between gender, age, ability, finishing times and consistency. We are going to use visualizations and statistics to support our claims.

Dataset

The dataset used for this study is available at https://www.kaggle.com/rojour/boston-results .

It consists of 26400 observations (athletes), each of which containing name, age, gender, country, city and state (where available), times at 9 different stages of the race, finish time and pace, overall place, gender place and division place.

Given the focus of our analysis, we’ll keep only information about the times, sex and gender of each athlete. The dataset is ordered by finishing time. Below can have a glimpse of the data of the first few runners to cross the line.

library(dplyr);
library(magrittr);
library(ggplot2);
library(lubridate);
library(readr);
df <- read.csv("./marathon_results_2017.csv", header=TRUE, stringsAsFactors=FALSE)
# Select only column
df <- df[c('Age', 'M.F', 'X5K', 'X10K', 'X15K', 'X20K', 'X25K', 'X30K', 'X35K', 'X40K', 'Official.Time')]
# Filter runners that had technical problems with recording apparatus
df %<>% filter(X5K != '-' & X10K != '-' & X15K != '-' & X20K != '-' & X25K != '-' & X30K != '-' & X35K != '-' & X40K != '-')
head(df)
print(paste("Number of runners: ", nrow(df)))
[1] "Number of runners:  26262"
print(paste("Number faulty observations: ", 26400 - nrow(df)))
[1] "Number faulty observations:  138"

As we can see, there are 8 columns that show the surpassed time at each 5K mark. Later we’ll do some pace variance analysis, so we’ll convert these times to be the time passed not from the start of the race, but from the mark of the previous 5K split.

This means that, for example, the X15K column will not tell how long it took the runner to run 15 kilometres, but how long it took the runner to run the last 5 kilomentres preceding the 15K mark, i.e., from 10K to 15K.

cols <- c('X5K', 'X10K', 'X15K', 'X20K', 'X25K', 'X30K', 'X35K', 'X40K')
df %<>% mutate_each_(funs(as.POSIXct(., format="%H:%M:%S")), cols);
df$X40K <- as.numeric(difftime(df$X40K, df$X35K, units='secs'))
df$X35K <- as.numeric(difftime(df$X35K, df$X30K, units='secs'))
df$X30K <- as.numeric(difftime(df$X30K, df$X25K, units='secs'))
df$X25K <- as.numeric(difftime(df$X25K, df$X20K, units='secs'))
df$X20K <- as.numeric(difftime(df$X20K, df$X15K, units='secs'))
df$X15K <- as.numeric(difftime(df$X15K, df$X10K, units='secs'))
df$X10K <- as.numeric(difftime(df$X10K, df$X5K, units='secs'))
df$X5K <- as.numeric(difftime(df$X5K, as.POSIXct('00:00:00', format="%H:%M:%S"), units='secs'))
colnames(df)[colnames(df) == 'M.F'] <- 'Gender'
head(df)

Now we can read the data frame above as: it took 925 seconds for runner #1 to run the first 5K of the race, 903s to run the next 5K, and so on.

But before we move to the analysis of the results, let’s take a look at the demographics of the population.

Inside a marathon there are many simultaneous events. There are men and women’s division, as well as 10 official divisons by age intervals. The main age group is the \([18, 39]\) year old, after that there are 9 age groups, but for sake of conciseness, we’ll divide athletes into 2 groups: young (belonging to the \([18, 39]\) age bracket), and 40+, for the remaining.

demo <- df %>%
  mutate(Gender, Gender = ifelse('M' == Gender,'MEN', 'WOMEN')) %>% 
  mutate(Age, Age = ifelse(Age >= 40, '40+', 'YOUNG')) %>% 
  group_by(Gender, Age) %>% 
  count()
demo$comb <- paste(demo$Age, demo$Gender)
# Pie Chart with Percentages
slices <- demo$n
lbls <- demo$comb
pct <- round(slices/sum(slices)*100)
lbls <- paste(lbls, pct) # add percents to labels 
lbls <- paste(lbls,"%",sep="") # ad % to labels 
pie(slices, labels = lbls, col=c("blue", "cyan", "violet", "pink"), main="Distribution of gender and age")

Given the pie chart above, we can see that our population composed of 55% men and 45% women.

As for age, 44% of people are in the “young” range, whilst the remaining are 40 years or older.

The largest group is 40+ men and the smallest is young men, which is interesting.

How gender and age influence finish-times

The graph below shows compares finishing times between men and women. Because the number of male and female runners differ, the histograms have been normalized.

The plot tells us that men tend to be faster than women. This is not unexpected, as there are physiological reasons why men are capable of faster finish-times than women.

df$Official.Time <- as.POSIXct(df$Official.Time, format="%H:%M:%S")
ggplot(df, aes(df$Official.Time, fill = df$Gender)) +
  geom_histogram(aes(y=..density..), alpha=0.5, 
                 position="identity", lwd=0.2) +
  ggtitle("Normalized finish-times for men vs. women")

df %>%
  mutate(Age, Age = ifelse(Age > 40, '40+', 'YOUNG')) %>% 
  ggplot(aes(Official.Time, fill = Age)) +
    geom_histogram(aes(y=..density..), alpha=0.5, 
                 position="identity", lwd=0.2) +
  ggtitle("Normalized finish-times for young vs. 40+")

n_groups <- 20
zebra_colormap <- rep(c("darkcyan", "cyan"), 20)
df <- df[df$Official.Time < quantile(df$Official.Time, 0.99), ]
#splits = split(df, cut(df$Official.Time, N))  # Time splits
#splits <- split(df, rep(1:ceiling(nrow(df)/N), each=N, length.out=nrow(df)))  # N marathoners splits
#df$group <- rep(1:ceiling(nrow(df)/n_groups), each=nrow(df)/n_groups, length.out=nrow(df))  # N marathoners splits
df$Official.Time <- as.numeric(difftime(df$Official.Time, as.POSIXct('00:00:00', format="%H:%M:%S"), units='mins'))
df$group <- cut(df$Official.Time, n_groups)
ggplot(df) + 
  geom_point(aes(x=1:NROW(df), y=df$Official.Time,  col=as.factor(df$group))) +
  scale_color_manual(values=zebra_colormap) +
  theme_bw()

Are women more disciplined than men?

women <- df %>%
  filter(Gender == 'F')
men <- df %>%
  filter(Gender == 'M')
b_splits = split(df, df$group)  # Time splits
w_splits = split(women, women$group)  # Time splits
m_splits = split(men, men$group)  # Time splits
g_sd_df <- data.frame("group" = numeric(0),
                      "gender" = character(0),
                      "n" = numeric(0),
                      "mean_sd" = numeric(0),
                      "sd_sd" = numeric(0),
                      stringsAsFactors = FALSE)
for (i in 1:n_groups) {
  gender <- 'M'
  mean_sd <- as.numeric(m_splits[[i]] %>%
                    select(cols) %>%
                    transform(SD=apply(., 1, sd, na.rm = TRUE)) %>%
                    summarize(sample_sd = mean(SD, na.rm = TRUE), sd(SD, na.rm = TRUE)))
  n <- as.numeric(m_splits[[i]] %>%
                    select(Official.Time) %>%
                    summarize(n = n()))
  g_sd_df[nrow(g_sd_df) + 1,] = c(i, gender, n, mean_sd, sd_sd)
  
  
  gender <- 'F'
  mean_sd <- as.numeric(w_splits[[i]] %>%
                    select(cols) %>%
                    transform(SD=apply(., 1, sd, na.rm = TRUE)) %>%
                    summarize(sample_sd = mean(SD, na.rm = TRUE), sd(SD, na.rm = TRUE)))
  n <- as.numeric(w_splits[[i]] %>%
                    select(Official.Time) %>%
                    summarize(n = n()))
  if (n != 0) {
    g_sd_df[nrow(g_sd_df) + 1,] = c(i, gender, n, mean_sd, sd_sd)
  }
  gender <- 'B'
  mean_sd <- as.numeric(b_splits[[i]] %>%
                    select(cols) %>%
                    transform(SD=apply(., 1, sd, na.rm = TRUE)) %>%
                    summarize(sample_sd = mean(SD, na.rm = TRUE)))
  n <- as.numeric(b_splits[[i]] %>%
                    select(Official.Time) %>%
                    summarize(n = n()))
  g_sd_df[nrow(g_sd_df) + 1,] = c(i, gender, n, mean_sd, sd_sd)
  
}
data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]data length [6] is not a sub-multiple or multiple of the number of columns [5]
g_sd_df$n <- as.numeric(g_sd_df$n)
g_sd_df$mean_sd <- as.numeric(g_sd_df$mean_sd)
g_sd_df$sd_sd <- as.numeric(g_sd_df$sd_sd)
g_sd_df$group <- as.numeric(g_sd_df$group)
g_sd_df
ggplot(g_sd_df) +
  geom_point(aes(x=group, y=mean_sd, colour=gender)) +
  scale_color_manual(values=c('black', 'violet', 'blue')) +
  theme_bw()

https://www.youtube.com/watch?v=d7nLL6cUC0I

g_sd_df <- filter(g_sd_df, g_sd_df$gender == 'M' | g_sd_df$gender == 'F')
g_sd_df$min_error <- ((g_sd_df$n-1) * (g_sd_df$mean_sd * g_sd_df$mean_sd)) / qchisq(.025, df = g_sd_df$n - 1, lower.tail = FALSE)
g_sd_df$max_error <- ((g_sd_df$n-1) * (g_sd_df$mean_sd * g_sd_df$mean_sd)) / qchisq(.975, df = g_sd_df$n - 1, lower.tail = FALSE)
g_sd_df
ggplot(g_sd_df, aes(x=group, y=mean_sd, colour=gender)) +
  geom_errorbar(aes(ymin=min_error, ymax=max_error)) +
  scale_color_manual(values=c('violet', 'blue')) +
  theme_bw()

corr <- df %>%
  select(cols) %>%
  transform(SD=apply(., 1, sd, na.rm = TRUE)) %>%
  select(c('SD')) %>%
  as.data.frame()
corr <- bind_cols(corr, select(df, c('Official.Time')))
ggplot(corr, aes(x=Official.Time, y=SD)) +
  #stat_density_2d(geom = "raster", aes(fill = ..density..), contour = FALSE)
  #stat_density_2d(aes(fill = ..level..), geom = "polygon")
  #stat_density_2d(geom = "point", aes(size = ..density..), n = 20, contour = FALSE)
  geom_hex(binwidth = c(1, 20)) + 
  theme_bw()

if (!file.exists('animation.gif')) {
  library(animation)
  n_samples <- 20
  sample <- df %>%
    group_by(group) %>%
    sample_n(n_samples, replace=TRUE)
  
  times <- t(data.matrix(select(sample, cols)))
  
  makeplot <- function() {
    for(i in 1:nrow(sample)) {
    
      plot.ts(times[cols,1:i], 
              plot.type="single", 
              lwd=0.5, 
              col=rep(rainbow(n_groups), each=n_samples), 
              ylim=c(900, 3000), 
              xlab='', ylab='', axes = F)
      
      lines(times[cols,i], 
            lwd=2, col=1, 
            xlab='', ylab='', axes = F)
      
      title(main="5K pace analysis", sub=paste('Group rank #', as.character(ceiling(i/n_samples))), xlab="", ylab="Split time (seconds)")
      axis(side=2,at=c(800, 1000, 1500, 2000, 2500, 3000),labels=c('800', '1000', '1500', '2000', '2500', '3000'))
      axis(side=1,at=c(-10,1,2,3,4,5,6,7,8),labels=c('','5K', '10K', '15K', '20K', '25K', '30K', '35K', '40K'))
    }
  }
  oopt = ani.options(interval = 0, nmax = n_runners)
  saveGIF(makeplot(),interval = 0.1, width = 580, height = 400)
  ani.options(oopt)
}
LS0tCnRpdGxlOiAiMjAxNyBCb3N0b24gTWFyYXRob24gQW5hbHlzaXMiCmF1dGhvcjogIkVkdWFyZG8gRmFjY2luIFZlcm5pZXIiCmRhdGU6ICJEZWNlbWJlciAyMDE3IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIEludHJvZHVjdGlvbgoKTGVnZW5kIHNheXMgdGhhdCBhZnRlciB0aGUgbWlsaXRhcnkgdmljdG9yeSBhZ2FpbnN0IHRoZSBQZXJzaWFucyBhdCB0aGUgQmF0dGxlIG9mIE1hcmF0aG9uICg0OTAgQkMpLCBHcmVlayBzb2xkaWVyIFBoZWlkaXBwaWRlcyByYW4gdGhlIDQyLjE5NSBraWxvbWV0cmVzIHRoYXQgc2VwYXJhdGUgTWFyYXRob24gYW5kIEF0aGVucyB0byByZXBvcnQgdGhlIHZpY3RvcnkuIFdoYXQgcGVvcGxlIHNlZW0gdG8gZm9yZ2V0IGFib3V0IHRoaXMgZmFibGUgaXMgdGhhdCBQaGVpZGlwcGlkZXMgZGllZCAqaW1tZWRpYXRlbHkqIGFmdGVyIGRlbGl2ZXJpbmcgdGhlIG1lc3NhZ2UuIEFuZCBzbyBsb25nLWRpc3RhbmNlIHJ1bm5pbmcgYXMgd2Uga25vdyB3YXMgYm9ybi4uLgoKClRoaXMgd29yayBhbmFseXNlcyB0aGUgMjAxNyBCb3N0b24gTWFyYXRob24gdGhhdCB0b29rIHBsYWNlIG9uIEFwcmlsIDE3dGguIFVzaW5nIGRhdGEgY29sbGVjdGVkIGZyb20gMjYsNDAwIGF0aGxldGVzLCB3ZSB3aWxsIHRyeSB0byBpZGVudGlmeSByZWxhdGlvbnNoaXBzIGJldHdlZW4gZ2VuZGVyLCBhZ2UsIGFiaWxpdHksIGZpbmlzaGluZyB0aW1lcyBhbmQgY29uc2lzdGVuY3kuIFdlIGFyZSBnb2luZyB0byB1c2UgdmlzdWFsaXphdGlvbnMgYW5kIHN0YXRpc3RpY3MgdG8gc3VwcG9ydCBvdXIgY2xhaW1zLgoKIVtdKGNvdXJzZW1hcC5qcGcpCgojIERhdGFzZXQKClRoZSBkYXRhc2V0IHVzZWQgZm9yIHRoaXMgc3R1ZHkgaXMgYXZhaWxhYmxlIGF0IGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vcm9qb3VyL2Jvc3Rvbi1yZXN1bHRzIC4KCkl0IGNvbnNpc3RzIG9mIDI2NDAwIG9ic2VydmF0aW9ucyAoYXRobGV0ZXMpLCBlYWNoIG9mIHdoaWNoIGNvbnRhaW5pbmcgbmFtZSwgYWdlLCBnZW5kZXIsIGNvdW50cnksIGNpdHkgYW5kIHN0YXRlICh3aGVyZSBhdmFpbGFibGUpLCB0aW1lcyBhdCA5IGRpZmZlcmVudCBzdGFnZXMgb2YgdGhlIHJhY2UsIGZpbmlzaCB0aW1lIGFuZCBwYWNlLCBvdmVyYWxsIHBsYWNlLCBnZW5kZXIgcGxhY2UgYW5kIGRpdmlzaW9uIHBsYWNlLgoKR2l2ZW4gdGhlIGZvY3VzIG9mIG91ciBhbmFseXNpcywgd2UnbGwga2VlcCBvbmx5IGluZm9ybWF0aW9uIGFib3V0IHRoZSB0aW1lcywgc2V4IGFuZCBnZW5kZXIgb2YgZWFjaCBhdGhsZXRlLgpUaGUgZGF0YXNldCBpcyBvcmRlcmVkIGJ5IGZpbmlzaGluZyB0aW1lLiBCZWxvdyBjYW4gaGF2ZSBhIGdsaW1wc2Ugb2YgdGhlIGRhdGEgb2YgdGhlIGZpcnN0IGZldyBydW5uZXJzIHRvIGNyb3NzIHRoZSBsaW5lLgoKYGBge3IgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgSW1wb3J0IGxpYnJhcmllcwpsaWJyYXJ5KGRwbHlyKTsKbGlicmFyeShtYWdyaXR0cik7CmxpYnJhcnkoZ2dwbG90Mik7CmxpYnJhcnkobHVicmlkYXRlKTsKbGlicmFyeShyZWFkcik7CmBgYAoKYGBge3J9CmRmIDwtIHJlYWQuY3N2KCIuL21hcmF0aG9uX3Jlc3VsdHNfMjAxNy5jc3YiLCBoZWFkZXI9VFJVRSwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKIyBTZWxlY3Qgb25seSBjb2x1bW4KZGYgPC0gZGZbYygnQWdlJywgJ00uRicsICdYNUsnLCAnWDEwSycsICdYMTVLJywgJ1gyMEsnLCAnWDI1SycsICdYMzBLJywgJ1gzNUsnLCAnWDQwSycsICdPZmZpY2lhbC5UaW1lJyldCgojIEZpbHRlciBydW5uZXJzIHRoYXQgaGFkIHRlY2huaWNhbCBwcm9ibGVtcyB3aXRoIHJlY29yZGluZyBhcHBhcmF0dXMKZGYgJTw+JSBmaWx0ZXIoWDVLICE9ICctJyAmIFgxMEsgIT0gJy0nICYgWDE1SyAhPSAnLScgJiBYMjBLICE9ICctJyAmIFgyNUsgIT0gJy0nICYgWDMwSyAhPSAnLScgJiBYMzVLICE9ICctJyAmIFg0MEsgIT0gJy0nKQpoZWFkKGRmKQpwcmludChwYXN0ZSgiTnVtYmVyIG9mIHJ1bm5lcnM6ICIsIG5yb3coZGYpKSkKcHJpbnQocGFzdGUoIk51bWJlciBmYXVsdHkgb2JzZXJ2YXRpb25zOiAiLCAyNjQwMCAtIG5yb3coZGYpKSkKYGBgCgpBcyB3ZSBjYW4gc2VlLCB0aGVyZSBhcmUgOCBjb2x1bW5zIHRoYXQgc2hvdyB0aGUgc3VycGFzc2VkIHRpbWUgYXQgZWFjaCA1SyBtYXJrLiBMYXRlciB3ZSdsbCBkbyBzb21lIHBhY2UgdmFyaWFuY2UgYW5hbHlzaXMsIHNvIHdlJ2xsIGNvbnZlcnQgdGhlc2UgdGltZXMgdG8gYmUgdGhlIHRpbWUgcGFzc2VkICoqbm90KiogZnJvbSB0aGUgc3RhcnQgb2YgdGhlIHJhY2UsIGJ1dCBmcm9tIHRoZSBtYXJrIG9mIHRoZSBwcmV2aW91cyA1SyBzcGxpdC4KClRoaXMgbWVhbnMgdGhhdCwgZm9yIGV4YW1wbGUsIHRoZSBYMTVLIGNvbHVtbiB3aWxsIG5vdCB0ZWxsIGhvdyBsb25nIGl0IHRvb2sgdGhlIHJ1bm5lciB0byBydW4gMTUga2lsb21ldHJlcywgYnV0IGhvdyBsb25nIGl0IHRvb2sgdGhlIHJ1bm5lciB0byBydW4gdGhlIGxhc3QgNSBraWxvbWVudHJlcyBwcmVjZWRpbmcgdGhlIDE1SyBtYXJrLCBpLmUuLCBmcm9tIDEwSyB0byAxNUsuIAoKCmBgYHtyIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjb2xzIDwtIGMoJ1g1SycsICdYMTBLJywgJ1gxNUsnLCAnWDIwSycsICdYMjVLJywgJ1gzMEsnLCAnWDM1SycsICdYNDBLJykKZGYgJTw+JSBtdXRhdGVfZWFjaF8oZnVucyhhcy5QT1NJWGN0KC4sIGZvcm1hdD0iJUg6JU06JVMiKSksIGNvbHMpOwoKZGYkWDQwSyA8LSBhcy5udW1lcmljKGRpZmZ0aW1lKGRmJFg0MEssIGRmJFgzNUssIHVuaXRzPSdzZWNzJykpCmRmJFgzNUsgPC0gYXMubnVtZXJpYyhkaWZmdGltZShkZiRYMzVLLCBkZiRYMzBLLCB1bml0cz0nc2VjcycpKQpkZiRYMzBLIDwtIGFzLm51bWVyaWMoZGlmZnRpbWUoZGYkWDMwSywgZGYkWDI1SywgdW5pdHM9J3NlY3MnKSkKZGYkWDI1SyA8LSBhcy5udW1lcmljKGRpZmZ0aW1lKGRmJFgyNUssIGRmJFgyMEssIHVuaXRzPSdzZWNzJykpCmRmJFgyMEsgPC0gYXMubnVtZXJpYyhkaWZmdGltZShkZiRYMjBLLCBkZiRYMTVLLCB1bml0cz0nc2VjcycpKQpkZiRYMTVLIDwtIGFzLm51bWVyaWMoZGlmZnRpbWUoZGYkWDE1SywgZGYkWDEwSywgdW5pdHM9J3NlY3MnKSkKZGYkWDEwSyA8LSBhcy5udW1lcmljKGRpZmZ0aW1lKGRmJFgxMEssIGRmJFg1SywgdW5pdHM9J3NlY3MnKSkKZGYkWDVLIDwtIGFzLm51bWVyaWMoZGlmZnRpbWUoZGYkWDVLLCBhcy5QT1NJWGN0KCcwMDowMDowMCcsIGZvcm1hdD0iJUg6JU06JVMiKSwgdW5pdHM9J3NlY3MnKSkKY29sbmFtZXMoZGYpW2NvbG5hbWVzKGRmKSA9PSAnTS5GJ10gPC0gJ0dlbmRlcicKaGVhZChkZikKYGBgCgpOb3cgd2UgY2FuIHJlYWQgdGhlIGRhdGEgZnJhbWUgYWJvdmUgYXM6IGl0IHRvb2sgOTI1IHNlY29uZHMgZm9yIHJ1bm5lciAjMSB0byBydW4gdGhlIGZpcnN0IDVLIG9mIHRoZSByYWNlLCA5MDNzIHRvIHJ1biB0aGUgbmV4dCA1SywgYW5kIHNvIG9uLiAKCkJ1dCBiZWZvcmUgd2UgbW92ZSB0byB0aGUgYW5hbHlzaXMgb2YgdGhlIHJlc3VsdHMsIGxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBkZW1vZ3JhcGhpY3Mgb2YgdGhlIHBvcHVsYXRpb24uCgpJbnNpZGUgYSBtYXJhdGhvbiB0aGVyZSBhcmUgbWFueSBzaW11bHRhbmVvdXMgZXZlbnRzLiBUaGVyZSBhcmUgbWVuIGFuZCB3b21lbidzIGRpdmlzaW9uLCBhcyB3ZWxsIGFzIDEwIG9mZmljaWFsIGRpdmlzb25zIGJ5IGFnZSBpbnRlcnZhbHMuIFRoZSBtYWluIGFnZSBncm91cCBpcyB0aGUgJFsxOCwgMzldJCB5ZWFyIG9sZCwgYWZ0ZXIgdGhhdCB0aGVyZSBhcmUgOSBhZ2UgZ3JvdXBzLCBidXQgZm9yIHNha2Ugb2YgY29uY2lzZW5lc3MsIHdlJ2xsIGRpdmlkZSBhdGhsZXRlcyBpbnRvIDIgZ3JvdXBzOiB5b3VuZyAoYmVsb25naW5nIHRvIHRoZSAkWzE4LCAzOV0kIGFnZSBicmFja2V0KSwgYW5kIDQwKywgZm9yIHRoZSByZW1haW5pbmcuCgoKYGBge3J9CmRlbW8gPC0gZGYgJT4lCiAgbXV0YXRlKEdlbmRlciwgR2VuZGVyID0gaWZlbHNlKCdNJyA9PSBHZW5kZXIsJ01FTicsICdXT01FTicpKSAlPiUgCiAgbXV0YXRlKEFnZSwgQWdlID0gaWZlbHNlKEFnZSA+PSA0MCwgJzQwKycsICdZT1VORycpKSAlPiUgCiAgZ3JvdXBfYnkoR2VuZGVyLCBBZ2UpICU+JSAKICBjb3VudCgpCgpkZW1vJGNvbWIgPC0gcGFzdGUoZGVtbyRBZ2UsIGRlbW8kR2VuZGVyKQoKIyBQaWUgQ2hhcnQgd2l0aCBQZXJjZW50YWdlcwpzbGljZXMgPC0gZGVtbyRuCmxibHMgPC0gZGVtbyRjb21iCnBjdCA8LSByb3VuZChzbGljZXMvc3VtKHNsaWNlcykqMTAwKQpsYmxzIDwtIHBhc3RlKGxibHMsIHBjdCkgIyBhZGQgcGVyY2VudHMgdG8gbGFiZWxzIApsYmxzIDwtIHBhc3RlKGxibHMsIiUiLHNlcD0iIikgIyBhZCAlIHRvIGxhYmVscyAKcGllKHNsaWNlcywgbGFiZWxzID0gbGJscywgY29sPWMoImJsdWUiLCAiY3lhbiIsICJ2aW9sZXQiLCAicGluayIpLCBtYWluPSJEaXN0cmlidXRpb24gb2YgZ2VuZGVyIGFuZCBhZ2UiKQpgYGAKCkdpdmVuIHRoZSBwaWUgY2hhcnQgYWJvdmUsIHdlIGNhbiBzZWUgdGhhdCBvdXIgcG9wdWxhdGlvbiBjb21wb3NlZCBvZiA1NSUgbWVuIGFuZCA0NSUgd29tZW4uCgpBcyBmb3IgYWdlLCA0NCUgb2YgcGVvcGxlIGFyZSBpbiB0aGUgInlvdW5nIiByYW5nZSwgd2hpbHN0IHRoZSByZW1haW5pbmcgYXJlIDQwIHllYXJzIG9yIG9sZGVyLgoKVGhlIGxhcmdlc3QgZ3JvdXAgaXMgNDArIG1lbiBhbmQgdGhlIHNtYWxsZXN0IGlzIHlvdW5nIG1lbiwgd2hpY2ggaXMgaW50ZXJlc3RpbmcuCgojIEhvdyBnZW5kZXIgYW5kIGFnZSBpbmZsdWVuY2UgZmluaXNoLXRpbWVzCgpUaGUgZ3JhcGggYmVsb3cgc2hvd3MgY29tcGFyZXMgZmluaXNoaW5nIHRpbWVzIGJldHdlZW4gbWVuIGFuZCB3b21lbi4gQmVjYXVzZSB0aGUgbnVtYmVyIG9mIG1hbGUgYW5kIGZlbWFsZSBydW5uZXJzIGRpZmZlciwgdGhlIGhpc3RvZ3JhbXMgaGF2ZSBiZWVuIG5vcm1hbGl6ZWQuCgpUaGUgcGxvdCB0ZWxscyB1cyB0aGF0IG1lbiB0ZW5kIHRvIGJlIGZhc3RlciB0aGFuIHdvbWVuLiBUaGlzIGlzIG5vdCB1bmV4cGVjdGVkLCBhcyB0aGVyZSBhcmUgcGh5c2lvbG9naWNhbCByZWFzb25zIHdoeSBtZW4gYXJlIGNhcGFibGUgb2YgZmFzdGVyIGZpbmlzaC10aW1lcyB0aGFuIHdvbWVuLgoKCmBgYHtyfQpkZiRPZmZpY2lhbC5UaW1lIDwtIGFzLlBPU0lYY3QoZGYkT2ZmaWNpYWwuVGltZSwgZm9ybWF0PSIlSDolTTolUyIpCmdncGxvdChkZiwgYWVzKGRmJE9mZmljaWFsLlRpbWUsIGZpbGwgPSBkZiRHZW5kZXIpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5kZW5zaXR5Li4pLCBhbHBoYT0wLjUsIAogICAgICAgICAgICAgICAgIHBvc2l0aW9uPSJpZGVudGl0eSIsIGx3ZD0wLjIpICsKICBnZ3RpdGxlKCJOb3JtYWxpemVkIGZpbmlzaC10aW1lcyBmb3IgbWVuIHZzLiB3b21lbiIpCmBgYAoKCmBgYHtyfQpkZiAlPiUKICBtdXRhdGUoQWdlLCBBZ2UgPSBpZmVsc2UoQWdlID4gNDAsICc0MCsnLCAnWU9VTkcnKSkgJT4lIAogIGdncGxvdChhZXMoT2ZmaWNpYWwuVGltZSwgZmlsbCA9IEFnZSkpICsKICAgIGdlb21faGlzdG9ncmFtKGFlcyh5PS4uZGVuc2l0eS4uKSwgYWxwaGE9MC41LCAKICAgICAgICAgICAgICAgICBwb3NpdGlvbj0iaWRlbnRpdHkiLCBsd2Q9MC4yKSArCiAgZ2d0aXRsZSgiTm9ybWFsaXplZCBmaW5pc2gtdGltZXMgZm9yIHlvdW5nIHZzLiA0MCsiKQoKYGBgCgoKCmBgYHtyfQpuX2dyb3VwcyA8LSAyMAp6ZWJyYV9jb2xvcm1hcCA8LSByZXAoYygiZGFya2N5YW4iLCAiY3lhbiIpLCAyMCkKCmRmIDwtIGRmW2RmJE9mZmljaWFsLlRpbWUgPCBxdWFudGlsZShkZiRPZmZpY2lhbC5UaW1lLCAwLjk5KSwgXQoKCiNzcGxpdHMgPSBzcGxpdChkZiwgY3V0KGRmJE9mZmljaWFsLlRpbWUsIE4pKSAgIyBUaW1lIHNwbGl0cwojc3BsaXRzIDwtIHNwbGl0KGRmLCByZXAoMTpjZWlsaW5nKG5yb3coZGYpL04pLCBlYWNoPU4sIGxlbmd0aC5vdXQ9bnJvdyhkZikpKSAgIyBOIG1hcmF0aG9uZXJzIHNwbGl0cwojZGYkZ3JvdXAgPC0gcmVwKDE6Y2VpbGluZyhucm93KGRmKS9uX2dyb3VwcyksIGVhY2g9bnJvdyhkZikvbl9ncm91cHMsIGxlbmd0aC5vdXQ9bnJvdyhkZikpICAjIE4gbWFyYXRob25lcnMgc3BsaXRzCmRmJE9mZmljaWFsLlRpbWUgPC0gYXMubnVtZXJpYyhkaWZmdGltZShkZiRPZmZpY2lhbC5UaW1lLCBhcy5QT1NJWGN0KCcwMDowMDowMCcsIGZvcm1hdD0iJUg6JU06JVMiKSwgdW5pdHM9J21pbnMnKSkKZGYkZ3JvdXAgPC0gY3V0KGRmJE9mZmljaWFsLlRpbWUsIG5fZ3JvdXBzKQoKZ2dwbG90KGRmKSArIAogIGdlb21fcG9pbnQoYWVzKHg9MTpOUk9XKGRmKSwgeT1kZiRPZmZpY2lhbC5UaW1lLCAgY29sPWFzLmZhY3RvcihkZiRncm91cCkpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz16ZWJyYV9jb2xvcm1hcCkgKwogIHRoZW1lX2J3KCkKYGBgCgoKCkFyZSB3b21lbiBtb3JlIGRpc2NpcGxpbmVkIHRoYW4gbWVuPwpgYGB7cn0Kd29tZW4gPC0gZGYgJT4lCiAgZmlsdGVyKEdlbmRlciA9PSAnRicpCgptZW4gPC0gZGYgJT4lCiAgZmlsdGVyKEdlbmRlciA9PSAnTScpCgpiX3NwbGl0cyA9IHNwbGl0KGRmLCBkZiRncm91cCkgICMgVGltZSBzcGxpdHMKd19zcGxpdHMgPSBzcGxpdCh3b21lbiwgd29tZW4kZ3JvdXApICAjIFRpbWUgc3BsaXRzCm1fc3BsaXRzID0gc3BsaXQobWVuLCBtZW4kZ3JvdXApICAjIFRpbWUgc3BsaXRzCgoKZ19zZF9kZiA8LSBkYXRhLmZyYW1lKCJncm91cCIgPSBudW1lcmljKDApLAogICAgICAgICAgICAgICAgICAgICAgImdlbmRlciIgPSBjaGFyYWN0ZXIoMCksCiAgICAgICAgICAgICAgICAgICAgICAibiIgPSBudW1lcmljKDApLAogICAgICAgICAgICAgICAgICAgICAgIm1lYW5fc2QiID0gbnVtZXJpYygwKSwKICAgICAgICAgICAgICAgICAgICAgICJzZF9zZCIgPSBudW1lcmljKDApLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQoKZm9yIChpIGluIDE6bl9ncm91cHMpIHsKICBnZW5kZXIgPC0gJ00nCiAgbWVhbl9zZCA8LSBhcy5udW1lcmljKG1fc3BsaXRzW1tpXV0gJT4lCiAgICAgICAgICAgICAgICAgICAgc2VsZWN0KGNvbHMpICU+JQogICAgICAgICAgICAgICAgICAgIHRyYW5zZm9ybShTRD1hcHBseSguLCAxLCBzZCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKHNhbXBsZV9zZCA9IG1lYW4oU0QsIG5hLnJtID0gVFJVRSksIHNkKFNELCBuYS5ybSA9IFRSVUUpKSkKICBuIDwtIGFzLm51bWVyaWMobV9zcGxpdHNbW2ldXSAlPiUKICAgICAgICAgICAgICAgICAgICBzZWxlY3QoT2ZmaWNpYWwuVGltZSkgJT4lCiAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKG4gPSBuKCkpKQogIGdfc2RfZGZbbnJvdyhnX3NkX2RmKSArIDEsXSA9IGMoaSwgZ2VuZGVyLCBuLCBtZWFuX3NkLCBzZF9zZCkKICAKICAKICBnZW5kZXIgPC0gJ0YnCiAgbWVhbl9zZCA8LSBhcy5udW1lcmljKHdfc3BsaXRzW1tpXV0gJT4lCiAgICAgICAgICAgICAgICAgICAgc2VsZWN0KGNvbHMpICU+JQogICAgICAgICAgICAgICAgICAgIHRyYW5zZm9ybShTRD1hcHBseSguLCAxLCBzZCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKHNhbXBsZV9zZCA9IG1lYW4oU0QsIG5hLnJtID0gVFJVRSksIHNkKFNELCBuYS5ybSA9IFRSVUUpKSkKICBuIDwtIGFzLm51bWVyaWMod19zcGxpdHNbW2ldXSAlPiUKICAgICAgICAgICAgICAgICAgICBzZWxlY3QoT2ZmaWNpYWwuVGltZSkgJT4lCiAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKG4gPSBuKCkpKQogIGlmIChuICE9IDApIHsKICAgIGdfc2RfZGZbbnJvdyhnX3NkX2RmKSArIDEsXSA9IGMoaSwgZ2VuZGVyLCBuLCBtZWFuX3NkLCBzZF9zZCkKICB9CgogIGdlbmRlciA8LSAnQicKICBtZWFuX3NkIDwtIGFzLm51bWVyaWMoYl9zcGxpdHNbW2ldXSAlPiUKICAgICAgICAgICAgICAgICAgICBzZWxlY3QoY29scykgJT4lCiAgICAgICAgICAgICAgICAgICAgdHJhbnNmb3JtKFNEPWFwcGx5KC4sIDEsIHNkLCBuYS5ybSA9IFRSVUUpKSAlPiUKICAgICAgICAgICAgICAgICAgICBzdW1tYXJpemUoc2FtcGxlX3NkID0gbWVhbihTRCwgbmEucm0gPSBUUlVFKSkpCiAgbiA8LSBhcy5udW1lcmljKGJfc3BsaXRzW1tpXV0gJT4lCiAgICAgICAgICAgICAgICAgICAgc2VsZWN0KE9mZmljaWFsLlRpbWUpICU+JQogICAgICAgICAgICAgICAgICAgIHN1bW1hcml6ZShuID0gbigpKSkKICBnX3NkX2RmW25yb3coZ19zZF9kZikgKyAxLF0gPSBjKGksIGdlbmRlciwgbiwgbWVhbl9zZCwgc2Rfc2QpCiAgCn0KCgpnX3NkX2RmJG4gPC0gYXMubnVtZXJpYyhnX3NkX2RmJG4pCmdfc2RfZGYkbWVhbl9zZCA8LSBhcy5udW1lcmljKGdfc2RfZGYkbWVhbl9zZCkKZ19zZF9kZiRzZF9zZCA8LSBhcy5udW1lcmljKGdfc2RfZGYkc2Rfc2QpCmdfc2RfZGYkZ3JvdXAgPC0gYXMubnVtZXJpYyhnX3NkX2RmJGdyb3VwKQpnX3NkX2RmCmBgYAoKCmBgYHtyfQpnZ3Bsb3QoZ19zZF9kZikgKwogIGdlb21fcG9pbnQoYWVzKHg9Z3JvdXAsIHk9bWVhbl9zZCwgY29sb3VyPWdlbmRlcikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoJ2JsYWNrJywgJ3Zpb2xldCcsICdibHVlJykpICsKICB0aGVtZV9idygpCmBgYAoKCmh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZDduTEw2Y1VDMEkKYGBge3J9Cmdfc2RfZGYgPC0gZmlsdGVyKGdfc2RfZGYsIGdfc2RfZGYkZ2VuZGVyID09ICdNJyB8IGdfc2RfZGYkZ2VuZGVyID09ICdGJykKZ19zZF9kZiRtaW5fZXJyb3IgPC0gKChnX3NkX2RmJG4tMSkgKiAoZ19zZF9kZiRtZWFuX3NkICogZ19zZF9kZiRtZWFuX3NkKSkgLyBxY2hpc3EoLjAyNSwgZGYgPSBnX3NkX2RmJG4gLSAxLCBsb3dlci50YWlsID0gRkFMU0UpCmdfc2RfZGYkbWF4X2Vycm9yIDwtICgoZ19zZF9kZiRuLTEpICogKGdfc2RfZGYkbWVhbl9zZCAqIGdfc2RfZGYkbWVhbl9zZCkpIC8gcWNoaXNxKC45NzUsIGRmID0gZ19zZF9kZiRuIC0gMSwgbG93ZXIudGFpbCA9IEZBTFNFKQpnX3NkX2RmCgpnZ3Bsb3QoZ19zZF9kZiwgYWVzKHg9Z3JvdXAsIHk9bWVhbl9zZCwgY29sb3VyPWdlbmRlcikpICsKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPW1pbl9lcnJvciwgeW1heD1tYXhfZXJyb3IpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCd2aW9sZXQnLCAnYmx1ZScpKSArCiAgdGhlbWVfYncoKQpgYGAKCgpgYGB7cn0KY29yciA8LSBkZiAlPiUKICBzZWxlY3QoY29scykgJT4lCiAgdHJhbnNmb3JtKFNEPWFwcGx5KC4sIDEsIHNkLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBzZWxlY3QoYygnU0QnKSkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpCgpjb3JyIDwtIGJpbmRfY29scyhjb3JyLCBzZWxlY3QoZGYsIGMoJ09mZmljaWFsLlRpbWUnKSkpCgpnZ3Bsb3QoY29yciwgYWVzKHg9T2ZmaWNpYWwuVGltZSwgeT1TRCkpICsKICAjc3RhdF9kZW5zaXR5XzJkKGdlb20gPSAicmFzdGVyIiwgYWVzKGZpbGwgPSAuLmRlbnNpdHkuLiksIGNvbnRvdXIgPSBGQUxTRSkKICAjc3RhdF9kZW5zaXR5XzJkKGFlcyhmaWxsID0gLi5sZXZlbC4uKSwgZ2VvbSA9ICJwb2x5Z29uIikKICAjc3RhdF9kZW5zaXR5XzJkKGdlb20gPSAicG9pbnQiLCBhZXMoc2l6ZSA9IC4uZGVuc2l0eS4uKSwgbiA9IDIwLCBjb250b3VyID0gRkFMU0UpCiAgZ2VvbV9oZXgoYmlud2lkdGggPSBjKDEsIDIwKSkgKyAKICB0aGVtZV9idygpCmBgYAoKCmBgYHtyfQppZiAoIWZpbGUuZXhpc3RzKCdhbmltYXRpb24uZ2lmJykpIHsKICBsaWJyYXJ5KGFuaW1hdGlvbikKCiAgbl9zYW1wbGVzIDwtIDIwCiAgc2FtcGxlIDwtIGRmICU+JQogICAgZ3JvdXBfYnkoZ3JvdXApICU+JQogICAgc2FtcGxlX24obl9zYW1wbGVzLCByZXBsYWNlPVRSVUUpCiAgCiAgdGltZXMgPC0gdChkYXRhLm1hdHJpeChzZWxlY3Qoc2FtcGxlLCBjb2xzKSkpCiAgCiAgbWFrZXBsb3QgPC0gZnVuY3Rpb24oKSB7CiAgICBmb3IoaSBpbiAxOm5yb3coc2FtcGxlKSkgewogICAgCiAgICAgIHBsb3QudHModGltZXNbY29scywxOmldLCAKICAgICAgICAgICAgICBwbG90LnR5cGU9InNpbmdsZSIsIAogICAgICAgICAgICAgIGx3ZD0wLjUsIAogICAgICAgICAgICAgIGNvbD1yZXAocmFpbmJvdyhuX2dyb3VwcyksIGVhY2g9bl9zYW1wbGVzKSwgCiAgICAgICAgICAgICAgeWxpbT1jKDkwMCwgMzAwMCksIAogICAgICAgICAgICAgIHhsYWI9JycsIHlsYWI9JycsIGF4ZXMgPSBGKQogICAgICAKICAgICAgbGluZXModGltZXNbY29scyxpXSwgCiAgICAgICAgICAgIGx3ZD0yLCBjb2w9MSwgCiAgICAgICAgICAgIHhsYWI9JycsIHlsYWI9JycsIGF4ZXMgPSBGKQogICAgICAKICAgICAgdGl0bGUobWFpbj0iNUsgcGFjZSBhbmFseXNpcyIsIHN1Yj1wYXN0ZSgnR3JvdXAgcmFuayAjJywgYXMuY2hhcmFjdGVyKGNlaWxpbmcoaS9uX3NhbXBsZXMpKSksIHhsYWI9IiIsIHlsYWI9IlNwbGl0IHRpbWUgKHNlY29uZHMpIikKICAgICAgYXhpcyhzaWRlPTIsYXQ9Yyg4MDAsIDEwMDAsIDE1MDAsIDIwMDAsIDI1MDAsIDMwMDApLGxhYmVscz1jKCc4MDAnLCAnMTAwMCcsICcxNTAwJywgJzIwMDAnLCAnMjUwMCcsICczMDAwJykpCiAgICAgIGF4aXMoc2lkZT0xLGF0PWMoLTEwLDEsMiwzLDQsNSw2LDcsOCksbGFiZWxzPWMoJycsJzVLJywgJzEwSycsICcxNUsnLCAnMjBLJywgJzI1SycsICczMEsnLCAnMzVLJywgJzQwSycpKQogICAgfQogIH0KICBvb3B0ID0gYW5pLm9wdGlvbnMoaW50ZXJ2YWwgPSAwLCBubWF4ID0gbl9ydW5uZXJzKQogIHNhdmVHSUYobWFrZXBsb3QoKSxpbnRlcnZhbCA9IDAuMSwgd2lkdGggPSA1ODAsIGhlaWdodCA9IDQwMCkKICBhbmkub3B0aW9ucyhvb3B0KQp9CmBgYAoKCiFbXShhbmltYXRpb24uZ2lmKQoKIAoKCgoKCg==